/*
 Copyright (c) 2011, fisamo
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.
 Redistributions in binary form must reproduce the above copyright notice, this
 list of conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.
 Neither the name of the fisamo nor the names of its contributors may be used
 to endorse or promote products derived from this software without specific
 prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <vector>

#include "source/Defs.h"

#include "source/ExpectedEntropies.h"
#include "source/ModifiedGaussianWindows.h"
#include "source/EntropyStruct.h"
#include "source/lme.h"

#include "opencv/cv.hpp"

FLOAT Log2(FLOAT n)
{
    // log(n)/log(2) is log2.
    return log(n) / log(2);
}

namespace lme
{
Clme::Clme()
: m_ConvexTolerance(0)
, m_ConcaveTolerance(0)
, m_PixelDistance(0)
, m_EntropyList()

{
	_TRACE;
}

Clme::~Clme()
{
	_TRACE;
}

VOID Clme::Inicio(const cv::Mat & aImage)
{
	_TRACE;
	cv::Mat copy = aImage.clone();
	Tracing(copy);
//	Window3(copy);
}

FLOAT Clme::CalculateEntropy(const FLOAT & aEntropy) const
{
	_TRACE;
	FLOAT p0 = aEntropy, p1 = 1 - p0;

	return -(p0 * Log2(p0) + p1 * Log2(p1));
}

VOID Clme::CalculatePointStrength()
{
	_TRACE;
	std::vector<lme::CEntropyStruct*>::iterator it = m_EntropyList.begin();
	std::vector<lme::CEntropyStruct*>::const_iterator it_end = m_EntropyList.end();

	/* For every candidate: */
	for(;it != it_end;++it)
	{
		lme::CEntropyStruct *entropy = *it;
		cv::Vec6f vec = entropy->GetEntropy();

		// Calculae average speed
		FLOAT mv = vec.dot(cv::Vec6f::all(1.0)) / 6.0;

		// Calculate average acceleration
		FLOAT ma = (vec[1] - vec[5]) / 6.0;

		// Set point strength as avg speed times avg acceleration
		entropy->SetPointStrength(mv * ma);
	}
}

Angle Clme::CalculateAngle(Point ap1, Point ap2, Point ap3) const
{
	Point median = cv::Point2f((ap1.x + ap2.x)/2, (ap1.y + ap2.y)/2);

	Angle radians = std::atan2((ap3.y - median.y), (ap3.x - median.x));

	return radians * 180.0 / PI;
}

VOID Clme::CalculateDominatPoint()
{	_TRACE;
	Pixel pixelDistance = GetPixelDistance();

	std::vector<lme::CEntropyStruct*>::iterator it = m_EntropyList.begin();
	std::vector<lme::CEntropyStruct*>::const_iterator it_end =
			m_EntropyList.end();

	std::vector<lme::CEntropyStruct*> newEntropyList;

	// i2t points to past element, it points to current element
	std::vector<lme::CEntropyStruct*>::iterator i2t = it++;

	/* For every candidate: */
	for(;it != it_end;++it)
	{

		Point p1 = (*it)->Coordinate(), p2 = (*i2t)->Coordinate();

		Pixel distance = ManhattanDistance(p1, p2);

		if(distance < pixelDistance)
		{
			// Calculate dominant candidate
			if((*it)->GetPointStrength() < (*i2t)->GetPointStrength())
				newEntropyList.push_back((*i2t));
			else
				newEntropyList.push_back((*it));
		}
	}
	// Substitute old list with new list
	m_EntropyList = newEntropyList;
}

Pixel Clme::ManhattanDistance(Point aPt1, Point aPt2) const
{
	_TRACE;
	INT dx = aPt1.x - aPt2.x, dy = aPt1.y - aPt2.y;

	return std::abs(dx + dy);
}

VOID Clme::AdjustEntropies(Tolerance aTolx, Tolerance aToly)
{
	_TRACE;
	std::vector<lme::CEntropyStruct*>::iterator it = m_EntropyList.begin();
	std::vector<lme::CEntropyStruct*>::const_iterator it_end = m_EntropyList.end();

	/* For every candidate: */
	for(;it != it_end;++it)
		AdjustEntropy(*it);
}

VOID Clme::AdjustEntropy(lme::CEntropyStruct * aEntropyStruct)
{
	_TRACE;
	const Entropy e = aEntropyStruct->GetEntropy();

	for(INT i = 0; i < (INT) SIZEOF(e); i++)
	{
		FLOAT entropy = e[i];

		// Check if candidate is convex
		if(entropy > ee[i][4])
		{
			if(entropy < (ee[i][4] + ee[i][3])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][4]);
			else if (entropy < (ee[i][3] + ee[i][2])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][3]);
			else if (entropy < (ee[i][2] + ee[i][1])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][2]);
			else if (entropy < (ee[i][1] + ee[i][0])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][1]);
			else
				aEntropyStruct->SetEntropy(i, ee[i][0]);
		}

		// Check if candidate is concave
		else if(entropy < ee[i][5])
		{
			if(entropy > (ee[i][5] + ee[i][6])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][5]);
			else if (entropy > (ee[i][6] + ee[i][7])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][3]);
			else if (entropy < (ee[i][2] + ee[i][1])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][2]);
			else if (entropy < (ee[i][1] + ee[i][0])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][1]);
			else
				aEntropyStruct->SetEntropy(i, ee[i][0]);
		}

		// Check if candidate is diagonal or horizontal/vertical segment
		else
		{
			if (entropy > (ee[i][4] + ee[i][5])/2.0)
				aEntropyStruct->SetEntropy(i, ee[i][4]);
			else
				aEntropyStruct->SetEntropy(i, ee[i][5]);
		}
	}
}

VOID Clme::SetPixelDistance(Pixel aPixel)
{
	_TRACE;
	m_PixelDistance = aPixel;
}

Pixel Clme::GetPixelDistance() const
{
	_TRACE;
	return m_PixelDistance;
}

VOID Clme::Tracing(cv::Mat & aImg_rgb)
{
	_TRACE;
	cv::Mat im_gray, im_smooth,	im_bw, im_er;

	// turn the input image in binary (object=1,back=0)
	cv::cvtColor(aImg_rgb, im_gray, CV_RGB2GRAY);

	cv::medianBlur(im_gray, im_smooth, 5);

	cv::morphologyEx(im_smooth, im_er, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1, -1), 2, cv::BORDER_REPLICATE);

	cv::threshold(im_er, im_bw, 0.0, 255.0, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);

	aImg_rgb = im_bw;
}

VOID Clme::Windows(cv::Mat & aBinaryImg)
{
	_TRACE;
	cv::Mat temp;

	std::vector<lme::CEntropyStruct*>::iterator it = m_EntropyList.begin();
	std::vector<lme::CEntropyStruct*>::const_iterator it_end = m_EntropyList.end();

	FLOAT entropy = 0.0;

	/* Add possible candidate to list */
	for(;it != it_end;++it)
	{
		// Get Point coordinates of corner candidate
		const cv::Point pt = (*it)->Coordinate();

		// Build a ROI of 1x1 around point location
		cv::Mat point(aBinaryImg, cv::Rect(pt.x, pt.y, 1, 1));

		// Calculate candidate entropies on all windows sizes
		for(INT i = 0;i < EntropiesCount;i++)
		{
			// Make diffusion happen...
			cv::filter2D(point, temp, -1, window[i], cv::Point(-1, -1), 0.0, cv::BORDER_CONSTANT);

			entropy = CalculateEntropy(temp.at<FLOAT>(pt));

			(*it)->SetEntropy(i, entropy);
		}
	}
}

VOID Clme::Window3(cv::Mat & aBinaryImg)
{
	_TRACE;
	cv::Mat temp;

	// Filter with 3x3 window...
	cv::filter2D(aBinaryImg, temp, -1, window[mg3], cv::Point(-1, -1), 0.0, cv::BORDER_CONSTANT);

	cv::MatIterator_<FLOAT> it = temp.begin<FLOAT>();
	cv::MatConstIterator_<FLOAT> it_end = temp.end<FLOAT>();

	FLOAT entropy = 0.0;
	for(;it != it_end;++it)
	{
		entropy = CalculateEntropy(*it);

		// Check if entropy corresponds to noise on image
		if((entropy > lme::Clme::MinimumEntropy) &&
				(entropy < lme::Clme::MaximumEntropy))
		{
			m_EntropyList.push_back(new CEntropyStruct(it.pos(), entropy));
		}
	}

}

VOID Clme::SetConcaveTolerance(const Tolerance & aConcaveTolerance)
{
	_TRACE;
	m_ConcaveTolerance = aConcaveTolerance;
}

VOID Clme::SetConvexTolerance(const Tolerance & aConvexTolerance)
{
	_TRACE;
	m_ConvexTolerance = aConvexTolerance;
}

Tolerance Clme::GetConcaveTolerance()
{
	_TRACE;
	return m_ConcaveTolerance;
}

Tolerance Clme::GetConvexTolerance()
{
	_TRACE;
	return m_ConvexTolerance;
}

VOID Clme::CalculateHistogram()
{
	_TRACE;
	cv::Mat src;
	// Entropy will be quantized to 8 levels
	INT entropyBins[] = {8};

	// Entropy varies from 0.51185 to 0.98326 on 35x35 window and zero tolerance
	FLOAT entropyRanges[] = {0.51185, 98326};
	const FLOAT * ranges[] = {entropyRanges};

	INT channels[] = {0};

	cv::calcHist(&src, 1, channels, cv::Mat(), // Do not use mask
			hist, 2, entropyBins, ranges,
			TRUE,  // The histogram is uniform
			FALSE);
}

BOOL Clme::Match()
{
	_TRACE;
	if( cv::compareHist(hist, standard, CV_COMP_BHATTACHARYYA) > 0.55)
		return true;
	else
		return false;
}
}
